package com.log320.huffman;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;

/**
 * Wrapper around a BufferedInputStream to handle reading of bits.
 * Used composition instead of inheritance to avoid coupling with the java API
 * 
 * The majority of the methods in this class come directly or have been slightly
 * adapted from source code from Princeton University
 * http://algs4.cs.princeton.edu/55compression/BinaryStdIn.java.html
 * 
 * @author Justin Lauziere
 *
 */
public class BitInputStream {
	private static BufferedInputStream in;
	private static final int EOF = -1;  // end of file
	private static int buffer;     		// 8-bit buffer of bits to write out
    private static int N;          		// number of bits remaining in buffer
    
    
    public BitInputStream(BufferedInputStream bis) { 
    	if (bis==null){
    		in = new BufferedInputStream(System.in);
    	}else{
    		in = bis;
    	}
    	fillBuffer();
    }
    /**
     * Read a byte and put it in the buffer
     */
    private static void fillBuffer() {
        try {
        	buffer = in.read();
        	N = 8;
        	}
        catch (IOException e) {
        	System.out.println("EOF");
        	buffer = EOF;
        	N = -1;
        	}
    }
    
    /**
     * Returns true if standard input is empty.
     * @return true if and only if standard input is empty
     */
    public static boolean isEmpty() {
        return buffer == EOF;
    }
    
    /**
     * Read the next bit of data from standard input and return as a boolean.
     * @return the next bit of data from standard input as a <tt>boolean</tt>
     * @throws Exception 
     * @throws RuntimeException if standard input is empty
     */
    public static boolean readBit() throws Exception {
        if (isEmpty()) throw new Exception("Reading from empty input stream");
        N--;
        boolean bit = ((buffer >> N) & 1) == 1;
        if (N == 0) fillBuffer();
        return bit;
    }
    
    /**
     * Read the next 8 bits from standard input and return as an 8-bit char.
     * Note that <tt>char</tt> is a 16-bit type;
     * to read the next 16 bits as a char, use <tt>readChar(16)</tt>
     * @return the next 8 bits of data from standard input as a <tt>char</tt>
     * @throws RuntimeException if there are fewer than 8 bits available on standard input
     */
    public static char readChar() {
        if (isEmpty()) throw new RuntimeException("Reading from empty input stream");

        // special case when aligned byte
        if (N == 8) {
            int x = buffer;
            fillBuffer();
            return (char) (x & 0xff);
        }

        // combine last N bits of current buffer with first 8-N bits of new buffer
        int x = buffer;
        x <<= (8-N);
        int oldN = N;
        fillBuffer();
        if (isEmpty()) throw new RuntimeException("Reading from empty input stream");
        N = oldN;
        x |= (buffer >>> N);
        return (char) (x & 0xff);
        // the above code doesn't quite work for the last character if N = 8
        // because buffer will be -1
    }
    
    /**
     * Read the next 32 bits from standard input and return as a 32-bit int.
     * @return the next 32 bits of data from standard input as a <tt>int</tt>
     * @throws RuntimeException if there are fewer than 32 bits available on standard input
     */
    public static int readInt() {
        int x = 0;
        for (int i = 0; i < 4; i++) {
            char c = readChar();
            x <<= 8;
            x |= c;
        }
        return x;
    }
    
    /**
     * Read the next 8 bits from standard input and return as an 8-bit byte.
     * @return the next 8 bits of data from standard input as a <tt>byte</tt>
     * @throws RuntimeException if there are fewer than 8 bits available on standard input
     */
    public static byte readByte() {
        char c = readChar();
        byte x = (byte) (c & 0xff);
        return x;
    }
    
    public void close() {
        try { in.close(); }
        catch (IOException e) { e.printStackTrace(); }
    }
}